# entity/position.py
import json
import os
from enum import Enum, auto
from datetime import datetime
from typing import Optional, Dict, Any, List
from entity.account import Account  # 假设 account.py 在 entity 子目录


class PositionDirection(Enum):
    NONE = auto()
    LONG = auto()
    SHORT = auto()


class Position:
    _position_id_counter = 0
    SAVE_DIR = r"F:\personal\python-project\backtest-v1\backtest_file\positions"

    def __init__(self,
                 symbol: str,
                 account_ref: Account,
                 # 修改点: 接收开仓和平仓手续费率
                 open_commission_rate: float = 0.0008,  # 0.08%
                 close_commission_rate: float = 0.001,  # 0.1%
                 leverage: float = 1.0):
        self.symbol: str = symbol
        self.account: Account = account_ref
        # 修改点: 存储不同的手续费率
        self.open_commission_rate: float = open_commission_rate
        self.close_commission_rate: float = close_commission_rate
        self.leverage: float = leverage

        self.direction: PositionDirection = PositionDirection.NONE
        self.entry_price: float = 0.0
        self.entry_time: Optional[datetime] = None
        self.initial_size: float = 0.0
        self.current_size: float = 0.0
        self.current_market_price: float = 0.0
        self.last_update_time: Optional[datetime] = None
        self.realized_pnl: float = 0.0
        self.total_fees: float = 0.0
        self.is_open: bool = False
        self.position_id: Optional[int] = None
        self.transaction_log: List[Dict[str, Any]] = []

        os.makedirs(Position.SAVE_DIR, exist_ok=True)

    @property
    def average_entry_price(self) -> float:
        # ... (此方法不变) ...
        if not self.transaction_log or self.current_size == 0:
            return self.entry_price if self.is_open else 0.0
        total_value_executed = 0
        total_quantity_executed = 0
        for tx in self.transaction_log:
            tx_type_str = tx['type']
            if self.direction == PositionDirection.LONG and \
                    tx_type_str in ['OPEN_LONG', 'ADD_TO_LONG']:
                total_value_executed += tx['price'] * tx['size']
                total_quantity_executed += tx['size']
            elif self.direction == PositionDirection.SHORT and \
                    tx_type_str in ['OPEN_SHORT', 'ADD_TO_SHORT']:
                total_value_executed += tx['price'] * tx['size']
                total_quantity_executed += tx['size']
        if total_quantity_executed > 0:
            return total_value_executed / total_quantity_executed
        else:
            return self.entry_price

    @property
    def unrealized_pnl(self) -> float:
        # ... (此方法不变) ...
        if not self.is_open or self.current_size == 0:
            return 0.0
        pnl_per_unit = 0.0
        avg_entry = self.average_entry_price
        if self.direction == PositionDirection.LONG:
            pnl_per_unit = self.current_market_price - avg_entry
        elif self.direction == PositionDirection.SHORT:
            pnl_per_unit = avg_entry - self.current_market_price
        gross_pnl = pnl_per_unit * self.current_size * self.leverage
        return gross_pnl

    def _calculate_fee(self, price: float, size: float, is_opening: bool) -> float:
        """
        计算单次操作的手续费。
        :param price: 执行价格
        :param size: 执行数量
        :param is_opening: 布尔值，True 表示开仓操作，False 表示平仓或减仓操作
        """
        rate = self.open_commission_rate if is_opening else self.close_commission_rate
        return price * size * rate * self.leverage

    def _add_transaction(self, tx_type: str, price: float, size: float, fee: float, timestamp: datetime,
                         pnl: float = 0.0):
        # ... (此方法不变) ...
        self.transaction_log.append({
            "type": tx_type, "price": price, "size": size, "fee": fee,
            "pnl_on_this_tx": pnl, "timestamp": timestamp.isoformat()
        })

    def open(self, direction: PositionDirection, price: float, size: float, timestamp: datetime) -> bool:
        if self.is_open:
            if self.direction != direction:
                print(f"警告: 尝试开立 {direction.name} 仓位，但当前持有 {self.direction.name} 仓位。请先平仓。")
                return False
            else:  # 方向相同，视为加仓
                print(f"通知: 已有同向持仓，将视为加仓操作。")
                return self.adjust(size, price, timestamp, is_opening_adjustment=True)  # 加仓也视为“开”新的一部分

        self.direction = direction
        self.entry_price = price
        self.entry_time = timestamp
        self.initial_size = size
        self.current_size = size
        self.current_market_price = price
        self.last_update_time = timestamp
        self.is_open = True
        self.realized_pnl = 0.0
        self.total_fees = 0.0
        self.transaction_log = []

        Position._position_id_counter += 1
        self.position_id = Position._position_id_counter

        # 修改点: 调用 _calculate_fee 时指明是开仓
        fee = self._calculate_fee(price, size, is_opening=True)
        self.total_fees += fee

        tx_type = "OPEN_LONG" if direction == PositionDirection.LONG else "OPEN_SHORT"
        self._add_transaction(tx_type, price, size, fee, timestamp)

        print(
            f"仓位 {self.position_id} 已开立: {direction.name} {size:.4f} {self.symbol} @ {price:.4f} (手续费: {fee:.4f}) 时间: {timestamp}")
        return True

    def adjust(self, size_change: float, price: float, timestamp: datetime,
               is_opening_adjustment: bool = False) -> bool:
        """
        调整仓位大小。
        :param is_opening_adjustment: 如果为True，表示这是对一个已开仓位的“加仓”操作，使用开仓费率。
                                      如果为False，表示这是“减仓”（部分平仓），使用平仓费率。
        """
        if not self.is_open:
            print("警告: 无法调整仓位，当前没有持仓。")
            return False

        if self.current_size + size_change < 0:
            if abs(size_change) >= self.current_size and size_change < 0:
                return self.close(price, timestamp, reason="通过减仓操作完全平仓")
            print(f"警告: 无法减少数量 {abs(size_change):.4f}。当前持仓量为 {self.current_size:.4f}。")
            return False

        # 修改点: 根据调整类型确定费率
        # 加仓 (size_change > 0) 使用开仓费率
        # 减仓 (size_change < 0) 使用平仓费率
        is_actually_opening = size_change > 0 or is_opening_adjustment
        fee = self._calculate_fee(price, abs(size_change), is_opening=is_actually_opening)
        self.total_fees += fee

        tx_pnl_on_this_tx = 0.0
        tx_type = ""

        if size_change > 0:  # 增加仓位
            if self.direction == PositionDirection.LONG:
                tx_type = "ADD_TO_LONG"
            elif self.direction == PositionDirection.SHORT:
                tx_type = "ADD_TO_SHORT"
            self.current_size += size_change
            print(
                f"仓位 {self.position_id} 已调整 (增加): {size_change:.4f} {self.symbol} @ {price:.4f}。新持仓量: {self.current_size:.4f}。手续费: {fee:.4f}")
        elif size_change < 0:  # 减少仓位 (部分平仓)
            reduction_size = abs(size_change)
            avg_entry = self.average_entry_price
            if self.direction == PositionDirection.LONG:
                tx_type = "REDUCE_LONG"
                tx_pnl_on_this_tx = (price - avg_entry) * reduction_size * self.leverage - fee
            elif self.direction == PositionDirection.SHORT:
                tx_type = "REDUCE_SHORT"
                tx_pnl_on_this_tx = (avg_entry - price) * reduction_size * self.leverage - fee
            self.realized_pnl += tx_pnl_on_this_tx
            self.current_size -= reduction_size
            print(
                f"仓位 {self.position_id} 已调整 (减少): {reduction_size:.4f} {self.symbol} @ {price:.4f}。新持仓量: {self.current_size:.4f}。本次减仓盈亏: {tx_pnl_on_this_tx:.2f}。手续费: {fee:.4f}")

        self._add_transaction(tx_type, price, abs(size_change), fee, timestamp, tx_pnl_on_this_tx)
        self.last_update_time = timestamp
        self.current_market_price = price

        if self.current_size == 0:
            self.end(timestamp, "因减仓至零而平仓。")
        return True

    def update_market_price(self, price: float, timestamp: datetime):
        # ... (此方法不变) ...
        if self.is_open:
            self.current_market_price = price
            self.last_update_time = timestamp

    def close(self, price: float, timestamp: datetime, reason: str = "策略平仓信号") -> bool:
        if not self.is_open:
            return False

        # 修改点: 调用 _calculate_fee 时指明是平仓
        fee_on_close = self._calculate_fee(price, self.current_size, is_opening=False)
        self.total_fees += fee_on_close

        final_pnl_on_close_tx = 0.0
        avg_entry = self.average_entry_price
        if self.direction == PositionDirection.LONG:
            final_pnl_on_close_tx = (price - avg_entry) * self.current_size * self.leverage - fee_on_close
        elif self.direction == PositionDirection.SHORT:
            final_pnl_on_close_tx = (avg_entry - price) * self.current_size * self.leverage - fee_on_close

        self.realized_pnl += final_pnl_on_close_tx

        tx_type = "CLOSE_LONG" if self.direction == PositionDirection.LONG else "CLOSE_SHORT"
        # 注意：这里的 self.current_size 是平仓前的数量
        self._add_transaction(tx_type, price, self.current_size, fee_on_close, timestamp, final_pnl_on_close_tx)

        closed_size = self.current_size  # 记录被平掉的数量
        self.current_size = 0

        print(
            f"仓位 {self.position_id} 已平仓: {self.direction.name} {closed_size:.4f} {self.symbol} @ {price:.4f}。总已实现盈亏: {self.realized_pnl:.2f} (总手续费: {self.total_fees:.2f}) 时间: {timestamp}。原因: {reason}")
        self.end(timestamp, reason)
        return True

    def end(self, timestamp: datetime, reason: str):
        # ... (此方法不变, 但 to_dict 中会包含新的费率信息) ...
        if self.current_size == 0 and self.is_open:
            self.is_open = False
            position_summary = self.to_dict(reason)
            filepath = os.path.join(Position.SAVE_DIR, f"position_{self.position_id}.json")
            try:
                with open(filepath, 'w', encoding='utf-8') as f:
                    json.dump(position_summary, f, indent=4, ensure_ascii=False)
                print(f"仓位 {self.position_id} 详情已保存至 {filepath}")
            except IOError as e:
                print(f"保存仓位 {self.position_id} 至文件时出错: {e}")
            self.account.record_trade(position_summary, timestamp)
            self._reset_position_state_after_close()

    def _reset_position_state_after_close(self):
        # ... (此方法不变) ...
        self.direction = PositionDirection.NONE
        self.entry_price = 0.0
        self.entry_time = None
        self.initial_size = 0.0
        # self.realized_pnl = 0.0 # 这些值已记录，不一定需要重置，除非对象严格重用
        # self.total_fees = 0.0
        # self.transaction_log = []

    def to_dict(self, close_reason: Optional[str] = None) -> Dict[str, Any]:
        return {
            "position_id": self.position_id,
            "symbol": self.symbol,
            "direction": self.direction.name if self.direction else PositionDirection.NONE.name,
            "entry_price_initial": self.entry_price,
            "average_entry_price": self.average_entry_price,
            "entry_time": self.entry_time.isoformat() if self.entry_time else None,
            "initial_size_at_open": self.initial_size,
            "close_time": self.last_update_time.isoformat() if self.last_update_time else None,
            "close_reason": close_reason,
            "realized_pnl": round(self.realized_pnl, 4),
            "total_fees": round(self.total_fees, 4),
            "leverage": self.leverage,
            # 修改点: 可以选择性地在此处也记录单次费率，但 Position 对象本身已经存了
            "open_commission_rate_used": self.open_commission_rate,
            "close_commission_rate_used": self.close_commission_rate,
            "is_open_at_serialization": self.is_open,
            "transaction_log": self.transaction_log
        }